1 需求 对一批页面进行了内容压缩,想要测试压缩后的结果和压缩前的结果内容是否一致。这时候人肉已经不满足日益增长的页面数量的需要了。需要进行部分自动化。 希望可以得到一个类似于单元测试的结果输出,能直观看出前后的结果差异。
2 初步使用 PhantomJS 可以很方便的用来做浏览器模拟测试,网页截图等,它是一个命令行工具,执行JavaScript文件。
在使用的过程中,发现如下的问题:
生成的page对象,只能打开一个页面,需要打开另一个页面的话需要另外create一个page对象,同时page对象的使用不恰当的话,命令行不会报错的;
同时在evaluate中,使用console.log是无法打印出来东西的;
在PhantomJS的交互中,老出错定义一个page对象都出错;
整个PhantomJS执行的脚本中,无法共享NodeJS的模块;
这样就很不爽了,需要异步操作的库,比如Promise呢?
3 寻求出路 上面的问题的解决办法:
每打开一个页面,就先新建一个page对象
这个可以通过page.onConsoleMessage来获取 页面内部的console信息,类似的有onAlert,onPompt,onConfirm
这个没法玩
寻找其他的替代品
于是就有了能在NodeJS平台中跑的phantom,这里 有简单的区别说明,用法基本都差不多是PhantomJS的用法,这里我们使用phantomjs-node。
Node和npm的出现使得JavaScript的工具库出现了百花齐放、百家争鸣的景象,同一个库有很多个实现(比如Promise),同时你也可以根据自己的需要对开放的模块进行改装。比如基于PhantomJS的各种不同的封装,PhantomJS是命令行工具,PhantomJS-Node则是可以在Node平台跑的PhantomJS工具。
3.1 代码比较 一开始使用比较两个页面html的方式来判断页面差异,最后发现如下问题:
页面中会有不少请求的url带上了时间戳,导致diff差异大;
同时页面中有JS动态生成的内容,执行的时间差异会导致内容不一样;
页面差异粒度太细,玩不下去了
这部分的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 byMinCode :function ( ){ function getPageContent (url ){ var _page; return phantomObj.then (function (ph ){ return ph.createPage (); }).then (function (page ){ _page = page; return page.open (url) }).then (function (status ){ if (status === 'success' ){ return _page.evaluate(function ( ){ return document .documentElement .outerHTML ; }); }else { throw new Error ('fail to load page' ) } }).then (function (content ){ return content; }).catch (function (error ){ console .log (error) }); } var pathMaps = this .config .pathMaps ; var tasks = [],tmpTask; var taskResults = {}; pathMaps.forEach (function (pathMap,index ){ var srcPath = pathMap[0 ]; var dstPath = pathMap[1 ]; var tmpResult = { srcPath :srcPath, dstPath :dstPath }; taskResults[srcPath] = tmpResult; tmpTask = Promise .all ([getPageContent (srcPath),getPageContent (dstPath)]).then (function (results ){ var minifiedResults = results.map (function (content ){ return minifier (content,{ minifyCSS :true , minifyJS :true , }); }); tmpResult.srcContent = results[0 ]; tmpResult.dstContent = results[1 ]; tmpResult.isOriginEqual = tmpResult.srcContent === tmpResult.dstContent ; tmpResult.minSrcContent = minifiedResults[0 ]; tmpResult.minDstContent = minifiedResults[1 ]; tmpResult.isMinEqual = tmpResult.minSrcContent === tmpResult.minDstContent ; var dirName = sysPath.join (process.cwd (), 'output' , 'mincode' ); if (!sysFs.existsSync (dirName)){ mkdirp.sync (dirName); } sysFs.writeFileSync (sysPath.join (dirName, 'srcContent-' + index),tmpResult.srcContent ,'utf-8' ); sysFs.writeFileSync (sysPath.join (dirName, 'dstContent-' + index),tmpResult.dstContent ,'utf-8' ); sysFs.writeFileSync (sysPath.join (dirName, 'minSrcContent-' + index),tmpResult.minSrcContent ,'utf-8' ); sysFs.writeFileSync (sysPath.join (dirName, 'minDstContent-' + index),tmpResult.minDstContent ,'utf-8' ); }).catch (function (reason ){ console .error (reason) }); tasks.push (tmpTask); }); Promise .all (tasks).then (function ( ){ Object .keys (taskResults).forEach (function (key ){ var result = taskResults[key]; console .log (result.srcPath ) console .log (result.dstPath ) console .log (result.isOriginEqual ) console .log (result.isMinEqual ) }); process.exit (); }); }
3.2 渲染的图片比较 刚说PhantomJS擅长浏览器模拟和截图,模拟上面我们已经试过了,这里看看截图功能的使用。 在phantomjs-node中清一色的基于Promise的API,渲染图片的时候也是异步操作,跟PhantomJS中的使用有些差别。
这样我们就能方便的获得处理前后的页面的页面截图了,然后需要进行图片比较。 我们到npmjs.org ,去查找类似于”image diff”或者“image compar”的关键字,在得到的列表中找出我们喜欢的模块,当然node平台下的有图有示例代码的自然是优先的。 比如resemblejs ,就不错但是基于浏览器HTML5 API的,然后有一些基于Node的修改版
我们使用node-resemble 来试试,然后这个包需要依赖node-canvas ,然后就是神奇的依赖环境Windows,对于node-gyp和node-canvas来说都是很感人的,尤其是对于Win10来说。
本身机子安装了Visual Studio Express 2013,玩不起来,安装node-canvas会报错。
用Visual Studio Community 2015在线安装包,安装会报组件包找不到,怎么提供都不行。
用小水管下 Visual Studio Community 2015 Update 1 完整ISO(将近4G),安装还是会报组件包找不到,怎么着都不行。。。
用小水管继续下Visual Studio Community 2015 Update 2 完整ISO(将近7G),一路等待。
安装环境小结:参考 node-gyp 和 node-canvas 的安装说明。
安装Python 2.7
安装Visual Studio Community 2015 最新版,选Visual C++
配置环境变量 GYP_MSVS_VERSION=2015
执行安装npm install node-resemble --GTK_Root=D:\Applications\gtkplus-bundle-2.22.1-win64\
这部分的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 bySnapShot :function ( ){ function getPageContent (url ){ var _page; return phantomObj.then (function (ph ){ return ph.createPage (); }).then (function (page ){ _page = page; return page.open (url) }).then (function (status ){ if (status === 'success' ){ return _page; }else { throw new Error ('fail to load page' ) } }).catch (function (error ){ console .log (error) }); } var pathMaps = this .config .pathMaps ; var tasks = [],tmpTask; var taskResults = {}; pathMaps.forEach (function (pathMap,index ){ var srcPath = pathMap[0 ]; var dstPath = pathMap[1 ]; var tmpResult = { srcPath :srcPath, dstPath :dstPath }; taskResults[srcPath] = tmpResult; tmpTask = Promise .all ([getPageContent (srcPath),getPageContent (dstPath)]).then (function (results ){ var srcPage = results[0 ]; var dstPage = results[1 ]; var dirName = sysPath.join (process.cwd (), 'output' , 'snapshots' ); if (!sysFs.existsSync (dirName)){ mkdirp.sync (dirName); } tmpResult.srcSnapshot = sysPath.join (dirName,'srcSnapshot-' + index + '.png' ); tmpResult.dstSnapshot = sysPath.join (dirName,'dstSnapshot-' + index + '.png' ); tmpResult.snapshotDiff = sysPath.join (dirName,'snapshotDiff-' + index + '.png' ); return Promise .all ([srcPage.render (tmpResult.srcSnapshot ),dstPage.render (tmpResult.dstSnapshot )]).then (function ( ){ return new Promise (function (resolve,reject ){ var srcSnapshotData = sysFs.readFileSync (tmpResult.srcSnapshot ); var dstSnapshotData = sysFs.readFileSync (tmpResult.dstSnapshot ); resemble (srcSnapshotData).compareTo (dstSnapshotData).onComplete (function (data ){ var dataUrl = data.getImageDataUrl (); var prefix = "data:image/png;base64," ; var base64Data = dataUrl.slice (prefix.length ); var fileData = new Buffer (base64Data,'base64' ); sysFs.writeFileSync (tmpResult.snapshotDiff ,fileData); resolve (); }); }); }); }).catch (function (reason ){ console .error (reason) }); tasks.push (tmpTask); }); Promise .all (tasks).then (function ( ){ Object .keys (taskResults).forEach (function (key ){ var result = taskResults[key]; console .log (result.srcPath ) console .log (result.dstPath ) }); process.exit (); }); }
4 参考资料
PhantomJS
PhantomJS 教程
phantomjs-node
node-phantom
phantomjs入门学习笔记
phantomjs使用说明
利用nodejs+phantomjs+casperjs采集淘宝商品的价格
鼓捣phantomjs,做ajax网站的信息采集
NodeJS + PhantomJS 抓取页面信息以及截图
Resemble
Resemble.js github
CasperJS
SlimerJS
SpookyJS
PhantomCSS
Headless Testing
Related Projects
Moving from PhantomJS to node-webkit
从PhantomJS迁移到node-webkit:自动化测试框架简单比较
CSS Regression Testing
CSS Testing with PhantomCSS, PhantomJS, CasperJS and Grunt
CSS回归测试与Resemble.js
浏览器自动化测试初探 - 使用phantomjs与casperjs
image-diff
blink-diff